/*
 * Generic Server main file.
 */

/*
 *  Multithreades parallel processing, single process server.
 *  Best suitated for average duration of session with multiple
 *  I/O intesive processing.
 */

/* Includes */
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include <string.h>
#include<signal.h>
#include<fcntl.h>
#include<netinet/in.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/errno.h>
#include <sys/wait.h>
#include <sys/socket.h>   // For Socket Functions
#include <string.h>       // FOr String FUnctions
#include <pthread.h>      // For Threading FUnctions
#include <errno.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <sys/socket.h>
#include <sys/time.h>     //For timeval
#include <stdarg.h>

#include "sock.h"
#include "server_utils.h"
#include "admin_service.h"
#include "prov_service.h"
#include "bid_service.h"
#include "util.h"

/* Local defines */
#define MAX_LISTEN_QUEUE_LEN  99
#define MAX_REQUEST_LEN       1024


/* Local globals */
SERVER_GLOBALS g;


/* Shall need to create a linked list/array when we support multiple services
   simultaniously using select.
   Or may perhaps just use vfork and execv
*/


//Local structures



//Local routines
void init_server(int argc, char **argv);
void shutdown_server(int error, char* msg);

/* Routines */

/*
 * Server startup initialisation routine.
 */
void init_server (int argc, char** argv)
{
    /* getenv to read parameters like
       port number, ip address
       log file location,
       maximum concurrent session count
       license info etc.
    */

    g.server_host = NULL;

	g.graceful_shutdown_request = FALSE;

    g.curr_sessions_count = 0;
    g.active_sessions_watermark = 0;
    g.avg_session_duration = 0;
    g.max_session_duration = 0;
    g.total_sessions_served = 0;

    if (argc < 2){
        errexit("\nUsage: %s <port>", argv[0]);
    }

    g.bid_port = atoi(argv[1]);

    if (g.bid_port < 1024){
        shutdown_server(-1, "Please use non-standard port i.e. > 1024");
    }

    //g.mode = MODE_DYNAMIC_THREADS;
    if (g.mode == MODE_POOLED_THREADS){
        //Create thread pool
    }

    g.prov_port = g.bid_port + 1;
    g.admin_port = g.prov_port + 1;



    LOG(DEBUG, "Starting \n bid_server on port :%d \n admin_service on port: %d\n prov_service on port: %d",
    		g.bid_port, g.admin_port, g.prov_port);


    time(&g.server_start_time);

    /*
      ToDo : Add getenvs here to get following additional parameters.

       1. server ip (Atul) -- as example.
       rest (Hiral)
       2. bid_service port, admin_service_port, prov_service_port
       3. Thread pool size.
       4. Maximum allowed session idle time.
       5. Maximum allowed session time.
       6. Maximum login attempts per connection.
       7. Maximum wait time before gracefulshutdown.

       logger g.mode - remote/local
       logger ip
       logger port
    */

    set_signal_handler(SIGALRM, &sig_alrm);
    set_signal_handler(SIGCHLD, &sig_chld);
    set_signal_handler(SIGINT, &sig_int);
    set_signal_handler(SIGPIPE, &sig_pipe);

    return;
}

/*
 * 1. Open server socket.
 * 2. Open prov socket
 * 3. Open admin socket
 * 4. Add the sockets to listen_fd
 * 5. Perform a perpectual for on select
 * 6. Depending on the request type delegate work.
 */
void server_main ()
{
    int max_fd, nready;
    fd_set r_set, all_set;
    int idle_timeout_counter = 0;
    struct timeval idle_interval;

    FD_ZERO(&all_set);

    //ToDo (Hiral)-- This can be configuration parameter
    g.bid_fd = open_server_socket(g.server_host,
                                   g.bid_port, MAX_LISTEN_QUEUE_LEN);
    FD_SET(g.bid_fd, &all_set);

    g.prov_fd = open_server_socket(g.server_host, g.prov_port, 0);
    FD_SET(g.prov_fd, &all_set);

    g.admin_fd = open_server_socket(g.server_host,
                                  g.admin_port, 0);
    FD_SET(g.admin_fd, &all_set);

    max_fd = g.prov_fd;
    while (!g.graceful_shutdown_request) { //Perpectual server main loop
        r_set = all_set;

        //  ToDo : (Hiral) make it configurable
        idle_interval.tv_sec = 900;//15 minutes
        idle_interval.tv_usec = 0;
        errno = 0;

        nready = select(max_fd+1, &r_set, NULL, NULL, &idle_interval);

        //ToDo:(Atul) check if interrupted.
        if (errno == EBADF || errno == EINVAL)
        {
            LOG(ERROR, "%d %s", errno,  strerror(errno));
            continue;
        }else if (errno == EINTR){
            LOG(WARN, "%d %s", errno,  strerror(errno));
            LOG(WARN, "Interrupt encountered in select");
            continue;
        }

        if (FD_ISSET(g.bid_fd, &r_set)){
            idle_timeout_counter = 0;
            handle_bid_service_connect(g.bid_fd);
        }else if (FD_ISSET(g.prov_fd, &r_set)){
            idle_timeout_counter = 0;
            handle_prov_request(g.prov_fd);
        }else if (FD_ISSET(g.admin_fd, &r_set)){
            idle_timeout_counter = 0;
            handle_admin_request(g.admin_fd);
        }else{
            LOG(ERROR, "Select interrupted.");
            LOG (INFO, "Idle timeout counter %d", idle_timeout_counter++);

            //Perform some periodic low priority activities..
            //Can calculate the max idle time watermark...
        }
    }

    shutdown_server(SRVRE_GRACEFUL_SHUTDOWN, "Graceful shutdown");
}


int main (int argc, char *argv[])
{
    //server init routine shall read env. params and setup env.
    init_server(argc, argv);

    server_main();

    return 0;
}

